# Load packages
library(DESeq2) # ‘1.46.0’
library(dplyr)
library(ggplot2)
library(org.Hs.eg.db) # library(org.Mm.eg.db) for mouse
library(ComplexHeatmap)
library(circlize) # for color scales
library(grid) # for gpar()
library(EnhancedVolcano)
# Load count_matrix
args(read.delim) # header = TRUE by default
function (file, header = TRUE, sep = "\t", quote = "\"", dec = ".",
fill = TRUE, comment.char = "", ...)
NULL
rawCounts <- read.delim("http://genomedata.org/gen-viz-workshop/intro_to_deseq2/tutorial/E-GEOD-50760-raw-counts.tsv")
# Format rawCounts as required by DESeq2
# 1. Set gene ID column as rowname
rownames(rawCounts) <- rawCounts$Gene.ID
# DESeq2 expects the rawCounts to contain only raw, unnormalized integer values, with no non-numeric columns.
# 2. Remove columns with non numerical values
rawCounts_bkup <- rawCounts
rawCounts <- rawCounts[, -c(1, 2)]
class(rawCounts) # a data.frame
[1] "data.frame"
# 3. Convert rawCounts as a matrix
rawCounts <- as.matrix(rawCounts)
# Note: we can do both steps above in one line of code
# rawCounts <- as.matrix(rawCounts[, sapply(rawCounts, is.numeric)])
# checks
is.matrix(rawCounts) # should be TRUE
is.numeric(rawCounts) # should be TRUE
# # Filtering: remove genes with 0 reads in all samples.
dim(rawCounts)
[1] 65217 54
dim(rawCounts[which(rowSums(rawCounts)>0),])
[1] 44316 54
rawCounts <- rawCounts[which(rowSums(rawCounts)>0),]
# Load colData (a dataframe describing samples)
colData <- read.delim("http://genomedata.org/gen-viz-workshop/intro_to_deseq2/tutorial/E-GEOD-50760-experiment-design.tsv")
# Format colData as required by DESeq2
# 1. Set sample ID (here Run column) as rowname
rownames(colData) <- colData$Run
# 2. Remove unnecessary columns
# Tissue type is the primary condition of interest
# Individual IDs are treated as control variables to account for inter-individual variation
keep <- c("Sample.Characteristic.biopsy.site.", "Sample.Characteristic.individual.")
colData <- colData[, keep]
# 3. Rename columns
colnames(colData) <- c("tissue_type", "individual_id")
# 4. Verify that the sample IDs match exactly and are in the same order between rawCounts and colData
all(rownames(colData) == colnames(rawCounts)) # should return TRUE
[1] TRUE
head(colData)
# 5. convert both columns of colData as factors
class(colData$individual_id) # "character"
[1] "character"
colData$individual_id <- factor(colData$individual_id)
class(colData$individual_id) # "factor"
[1] "factor"
colData$tissue_type <- factor(colData$tissue_type)
# DESeq2 uses the first factor level as reference
levels(colData$tissue_type) # reference = "colorectal cancer metastatic in the liver"
[1] "colorectal cancer metastatic in the liver"
[2] "normal"
[3] "primary tumor"
# Comparisons: "normal" and "primary tumor" vs reference
# We want to compare "colorectal cancer metastatic in the liver" and "primary tumor" against "normal" instead
# 6. Change levels order such that reference = "normal"
levels(colData$tissue_type) <- c(
"normal",
"primary tumor",
"colorectal cancer metastatic in the liver"
)
levels(colData$tissue_type)
[1] "normal"
[2] "primary tumor"
[3] "colorectal cancer metastatic in the liver"
# Create the DEseq2DataSet object
# Design: control variable first, condition of interest second
# DESeq2 adjusts for individual variation before testing tissue_type differences
dds <- DESeqDataSetFromMatrix(countData = rawCounts, colData = colData, design = ~ individual_id + tissue_type)
dds # prints summary of the DESeqDataSet
class: DESeqDataSet
dim: 44316 54
metadata(1): version
assays(1): counts
rownames(44316): ENSG00000000003 ENSG00000000005 ... ENSG00000281918
ENSG00000281920
rowData names(0):
colnames(54): SRR975551 SRR975552 ... SRR975603 SRR975604
colData names(2): tissue_type individual_id
colData(dds) # shows sample annotations (e.g., tissue_type, individual_id)
DataFrame with 54 rows and 2 columns
tissue_type individual_id
<factor> <factor>
SRR975551 colorectal cancer metastatic in the liver AMC_2
SRR975552 colorectal cancer metastatic in the liver AMC_3
SRR975553 colorectal cancer metastatic in the liver AMC_5
SRR975554 colorectal cancer metastatic in the liver AMC_6
SRR975555 colorectal cancer metastatic in the liver AMC_7
... ... ...
SRR975600 normal AMC_20
SRR975601 normal AMC_21
SRR975602 normal AMC_22
SRR975603 normal AMC_23
SRR975604 normal AMC_24
counts(dds)[1:5, 1:5] # view first few genes and samples
SRR975551 SRR975552 SRR975553 SRR975554 SRR975555
ENSG00000000003 6617 1352 1492 3390 1464
ENSG00000000005 69 1 20 23 12
ENSG00000000419 2798 714 510 1140 1667
ENSG00000000457 486 629 398 239 383
ENSG00000000460 466 342 73 227 193
# Run the DESeq pipeline
dds <- DESeq(dds)
# Variance Stabilizing Transformation and plot PCA
vsd <- vst(dds, blind = FALSE)
plotPCA(vsd, intgroup="tissue_type")+ theme_grey()

# Note: contrast = c(factor, numerator, denominator)
# means Compute log2 fold change as numerator / denominator.
# contrast = c("tissue_type", "primary tumor", "normal"): positive values mean higher in tumor, negative mean higher in normal
# contrast = c("tissue_type", "normal", "primary tumor"): positive values mean higher in normal, negative mean higher in tumor
# Extract results
res1 <- results(dds, contrast = c("tissue_type", "primary tumor", "normal"))
res1
log2 fold change (MLE): tissue_type primary tumor vs normal
Wald test p-value: tissue type primary.tumor vs normal
DataFrame with 44316 rows and 6 columns
baseMean log2FoldChange lfcSE stat pvalue padj
<numeric> <numeric> <numeric> <numeric> <numeric> <numeric>
ENSG00000000003 1824.881 -0.441529 0.180629 -2.44439 1.45095e-02 3.48599e-02
ENSG00000000005 10.852 0.540095 0.435418 1.24040 2.14826e-01 3.30104e-01
ENSG00000000419 725.173 -1.009012 0.135824 -7.42881 1.09583e-13 3.96955e-12
ENSG00000000457 311.358 0.321168 0.113114 2.83934 4.52069e-03 1.26684e-02
ENSG00000000460 126.362 -1.357771 0.211021 -6.43429 1.24048e-10 2.26492e-09
... ... ... ... ... ... ...
ENSG00000281909 0.2767389 0.3742786 2.936548 0.1274553 0.898580 NA
ENSG00000281910 0.0537801 0.0416978 3.668177 0.0113674 0.990930 NA
ENSG00000281912 10.2974190 0.1583833 0.227051 0.6975657 0.485449 0.614118
ENSG00000281918 0.2310112 0.2723037 3.664070 0.0743173 0.940758 NA
ENSG00000281920 0.2112458 0.0399116 3.666410 0.0108857 0.991315 NA
# Remove rows with NA
res1 <- na.omit(res1)
res1.df <- as.data.frame(res1)
res1.df
# get gene names for gene IDs
res1.df$symbol <- mapIds(org.Hs.eg.db, keys = rownames(res1.df), keytype = "ENSEMBL", column = "SYMBOL")
res1.df <- res1.df[!is.na(res1.df$symbol),]
res2 <- results(dds, contrast = c("tissue_type", "colorectal cancer metastatic in the liver", "normal"))
res2 <- na.omit(res2)
res2.df <- as.data.frame(res2)
res2.df
res2.df$symbol <- mapIds(org.Hs.eg.db, keys = rownames(res2.df), keytype = "ENSEMBL", column = "SYMBOL")
res2.df <- res2.df[!is.na(res2.df$symbol),]
# Volcano plots 1
EnhancedVolcano(res1.df, x = 'log2FoldChange', y = 'padj', lab = res1.df$symbol,
pCutoff = 0.05, FCcutoff = 1)

# Volcano plots 2
EnhancedVolcano(res2.df, x = 'log2FoldChange', y = 'padj', lab = res2.df$symbol,
pCutoff = 0.05, FCcutoff = 1)

# Volcano plots using ggplot2
res1.df$expression <- "NOT"
res1.df$expression[res1.df$padj < 0.01 & res1.df$log2FoldChange > 2] <- "UP"
res1.df$expression[res1.df$padj < 0.01 & res1.df$log2FoldChange < -2] <- "DOWN"
ggplot(data = res1.df, aes(x = log2FoldChange, y = -log10(padj), col = expression)) +
geom_vline(xintercept = c(-2, 2), col = "gray", linetype = 'dashed') +
geom_hline(yintercept = -log10(0.01), col = "gray", linetype = 'dashed') +
geom_point(size = 2) +
scale_color_manual(values = c("#3498DB", "grey", "#E74C3C"),
labels = c("Downregulated", "Not significant", "Upregulated")) +
theme_classic()

res2.df$expression <- "NOT"
res2.df$expression[res2.df$padj < 0.01 & res2.df$log2FoldChange > 2] <- "UP"
res2.df$expression[res2.df$padj < 0.01 & res2.df$log2FoldChange < -2] <- "DOWN"
ggplot(data = res2.df, aes(x = log2FoldChange, y = -log10(padj), col = expression)) +
geom_vline(xintercept = c(-2, 2), col = "gray", linetype = 'dashed') +
geom_hline(yintercept = -log10(0.01), col = "gray", linetype = 'dashed') +
geom_point(size = 2) +
scale_color_manual(values = c("#3498DB", "grey", "#E74C3C"),
labels = c("Downregulated", "Not significant", "Upregulated")) +
theme_classic()

# Find significant genes
sigs1.df <- res1.df[res1.df$padj < 0.01,]
sigs1.df <- sigs1.df[(abs(sigs1.df$log2FoldChange) > 2),]
sigs1.df <- sigs1.df[sigs1.df$baseMean > 50,] # - average normalized count for a gene across all samples
sigs1.df
NA
NA
sigs2.df <- res2.df[res2.df$padj < 0.01,]
sigs2.df <- sigs2.df[(abs(sigs2.df$log2FoldChange) > 2),]
sigs2.df <- sigs2.df[sigs2.df$baseMean > 50,]
sigs2.df
# Combine significant genes from both comparisons
sigs_combined <- rbind(sigs1.df, sigs2.df)
# Get unique gene IDs
unique_genes <- unique(c(rownames(sigs1.df), rownames(sigs2.df)))
# Subset by unique rownames
sigs_combined <- sigs_combined[unique_genes, ]
# Extract normalized counts for those genes
mat <- counts(dds, normalized=T)[rownames(sigs_combined),] # or mat <- assay(vsd)[rownames(sigs_combined), ]
# Z-score transform each gene across samples
mat.z <- t(apply(mat, 1, scale))
colnames(mat.z) <- rownames(colData)
tissue <- colData$tissue_type
# Color map
tissue_color <- c(
"primary tumor" = "darkorange",
"normal" = "forestgreen",
"colorectal cancer metastatic in the liver" = "firebrick"
)
ha <- HeatmapAnnotation(
Tissue = tissue,
col = list(Tissue = tissue_color)
)
# Plot Heatmap
Heatmap(
mat.z,
name = "Z-score",
top_annotation = ha,
cluster_rows = TRUE,
cluster_columns = TRUE,
show_column_names = TRUE,
show_row_names = FALSE,
row_labels = sigs_combined[rownames(mat.z), "symbol"],# useful when above is TRUE
row_names_gp = gpar(fontsize = 6),
column_names_gp = gpar(fontsize = 6),
heatmap_legend_param = list(title = "Z-score", legend_direction = "horizontal")
)

# Get top 50 genes from both comparisons
topGenes1 <- head(sigs1.df[order(abs(sigs1.df$log2FoldChange), decreasing = TRUE), ], 50)
topGenes2 <- head(sigs2.df[order(abs(sigs2.df$log2FoldChange), decreasing = TRUE), ], 50)
# Combine both sets
topGenes_combined <- rbind(topGenes1, topGenes2)
# Get unique ENSEMBL IDs
unique_genes <- unique(c(rownames(topGenes1), rownames(topGenes2)))
# Subset by unique rownames
topGenes_combined <- topGenes_combined[unique_genes, ]
# Extract normalized counts for those genes
topmat <- counts(dds, normalized=T)[rownames(topGenes_combined),]
topmat.z <- t(apply(topmat, 1, scale)) # Z score transformation of genes across samples.
colnames(topmat.z) <- rownames(colData)
Heatmap(
topmat.z,
name = "Z-score",
top_annotation = ha,
cluster_rows = TRUE,
cluster_columns = TRUE,
show_column_names = TRUE,
show_row_names = TRUE,
row_labels = topGenes_combined[rownames(topmat.z), "symbol"],
row_names_gp = gpar(fontsize = 6),
column_names_gp = gpar(fontsize = 6),
heatmap_legend_param = list(title = "Z-score", legend_direction = "horizontal")
)

LS0tDQp0aXRsZTogIlIgTm90ZWJvb2siDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQpgYGB7cn0NCiMgTG9hZCBwYWNrYWdlcw0KbGlicmFyeShERVNlcTIpICMg4oCYMS40Ni4w4oCZDQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeShnZ3Bsb3QyKQ0KbGlicmFyeShvcmcuSHMuZWcuZGIpICMgbGlicmFyeShvcmcuTW0uZWcuZGIpIGZvciBtb3VzZQ0KbGlicmFyeShDb21wbGV4SGVhdG1hcCkNCmxpYnJhcnkoY2lyY2xpemUpICAgIyBmb3IgY29sb3Igc2NhbGVzDQpsaWJyYXJ5KGdyaWQpICAgICAgICMgZm9yIGdwYXIoKQ0KbGlicmFyeShFbmhhbmNlZFZvbGNhbm8pDQpgYGANCg0KYGBge3J9DQojIExvYWQgY291bnRfbWF0cml4DQphcmdzKHJlYWQuZGVsaW0pICMgaGVhZGVyID0gVFJVRSBieSBkZWZhdWx0DQpyYXdDb3VudHMgPC0gcmVhZC5kZWxpbSgiaHR0cDovL2dlbm9tZWRhdGEub3JnL2dlbi12aXotd29ya3Nob3AvaW50cm9fdG9fZGVzZXEyL3R1dG9yaWFsL0UtR0VPRC01MDc2MC1yYXctY291bnRzLnRzdiIpDQpgYGANCg0KYGBge3J9DQojIEZvcm1hdCByYXdDb3VudHMgYXMgcmVxdWlyZWQgYnkgREVTZXEyDQpgYGANCg0KYGBge3J9DQojIDEuIFNldCBnZW5lIElEIGNvbHVtbiBhcyByb3duYW1lDQpyb3duYW1lcyhyYXdDb3VudHMpIDwtIHJhd0NvdW50cyRHZW5lLklEDQoNCiMgREVTZXEyIGV4cGVjdHMgdGhlIHJhd0NvdW50cyB0byBjb250YWluIG9ubHkgcmF3LCB1bm5vcm1hbGl6ZWQgaW50ZWdlciB2YWx1ZXMsIHdpdGggbm8gbm9uLW51bWVyaWMgY29sdW1ucy4NCiMgMi4gUmVtb3ZlIGNvbHVtbnMgd2l0aCBub24gbnVtZXJpY2FsIHZhbHVlcyANCnJhd0NvdW50c19ia3VwIDwtIHJhd0NvdW50cw0KcmF3Q291bnRzIDwtIHJhd0NvdW50c1ssIC1jKDEsIDIpXQ0KDQpjbGFzcyhyYXdDb3VudHMpICMgYSBkYXRhLmZyYW1lDQojIDMuIENvbnZlcnQgcmF3Q291bnRzIGFzIGEgbWF0cml4DQpyYXdDb3VudHMgPC0gYXMubWF0cml4KHJhd0NvdW50cykNCmBgYA0KYGBge3J9DQojIE5vdGU6IHdlIGNhbiBkbyBib3RoIHN0ZXBzIGFib3ZlIGluIG9uZSBsaW5lIG9mIGNvZGUNCiMgcmF3Q291bnRzIDwtIGFzLm1hdHJpeChyYXdDb3VudHNbLCBzYXBwbHkocmF3Q291bnRzLCBpcy5udW1lcmljKV0pDQpgYGANCg0KYGBge3J9DQojIGNoZWNrcw0KaXMubWF0cml4KHJhd0NvdW50cykgIyBzaG91bGQgYmUgVFJVRQ0KaXMubnVtZXJpYyhyYXdDb3VudHMpICMgc2hvdWxkIGJlIFRSVUUNCmBgYA0KDQpgYGB7cn0NCiMgIyBGaWx0ZXJpbmc6IHJlbW92ZSBnZW5lcyB3aXRoIDAgcmVhZHMgaW4gYWxsIHNhbXBsZXMuDQpkaW0ocmF3Q291bnRzKQ0KZGltKHJhd0NvdW50c1t3aGljaChyb3dTdW1zKHJhd0NvdW50cyk+MCksXSkNCnJhd0NvdW50cyA8LSByYXdDb3VudHNbd2hpY2gocm93U3VtcyhyYXdDb3VudHMpPjApLF0NCmBgYA0KYGBge3J9DQojIExvYWQgY29sRGF0YSAoYSBkYXRhZnJhbWUgZGVzY3JpYmluZyBzYW1wbGVzKQ0KY29sRGF0YSA8LSByZWFkLmRlbGltKCJodHRwOi8vZ2Vub21lZGF0YS5vcmcvZ2VuLXZpei13b3Jrc2hvcC9pbnRyb190b19kZXNlcTIvdHV0b3JpYWwvRS1HRU9ELTUwNzYwLWV4cGVyaW1lbnQtZGVzaWduLnRzdiIpDQpgYGANCg0KYGBge3J9DQojIEZvcm1hdCBjb2xEYXRhIGFzIHJlcXVpcmVkIGJ5IERFU2VxMg0KYGBgDQoNCmBgYHtyfQ0KIyAxLiBTZXQgc2FtcGxlIElEIChoZXJlIFJ1biBjb2x1bW4pIGFzIHJvd25hbWUNCnJvd25hbWVzKGNvbERhdGEpIDwtIGNvbERhdGEkUnVuDQoNCiMgMi4gUmVtb3ZlIHVubmVjZXNzYXJ5IGNvbHVtbnMNCiMgVGlzc3VlIHR5cGUgaXMgdGhlIHByaW1hcnkgY29uZGl0aW9uIG9mIGludGVyZXN0DQojIEluZGl2aWR1YWwgSURzIGFyZSB0cmVhdGVkIGFzIGNvbnRyb2wgdmFyaWFibGVzIHRvIGFjY291bnQgZm9yIGludGVyLWluZGl2aWR1YWwgdmFyaWF0aW9uDQprZWVwIDwtIGMoIlNhbXBsZS5DaGFyYWN0ZXJpc3RpYy5iaW9wc3kuc2l0ZS4iLCAiU2FtcGxlLkNoYXJhY3RlcmlzdGljLmluZGl2aWR1YWwuIikNCmNvbERhdGEgPC0gY29sRGF0YVssIGtlZXBdDQoNCiMgMy4gUmVuYW1lIGNvbHVtbnMNCmNvbG5hbWVzKGNvbERhdGEpIDwtIGMoInRpc3N1ZV90eXBlIiwgImluZGl2aWR1YWxfaWQiKQ0KDQojICA0LiBWZXJpZnkgdGhhdCB0aGUgc2FtcGxlIElEcyBtYXRjaCBleGFjdGx5IGFuZCBhcmUgaW4gdGhlIHNhbWUgb3JkZXIgYmV0d2VlbiByYXdDb3VudHMgYW5kIGNvbERhdGENCmFsbChyb3duYW1lcyhjb2xEYXRhKSA9PSBjb2xuYW1lcyhyYXdDb3VudHMpKSAjIHNob3VsZCByZXR1cm4gVFJVRQ0KDQpoZWFkKGNvbERhdGEpDQpgYGANCmBgYHtyfQ0KIyA1LiBjb252ZXJ0IGJvdGggY29sdW1ucyBvZiBjb2xEYXRhIGFzIGZhY3RvcnMNCmNsYXNzKGNvbERhdGEkaW5kaXZpZHVhbF9pZCkgIyAiY2hhcmFjdGVyIg0KY29sRGF0YSRpbmRpdmlkdWFsX2lkIDwtIGZhY3Rvcihjb2xEYXRhJGluZGl2aWR1YWxfaWQpDQpjbGFzcyhjb2xEYXRhJGluZGl2aWR1YWxfaWQpICMgImZhY3RvciINCmNvbERhdGEkdGlzc3VlX3R5cGUgPC0gZmFjdG9yKGNvbERhdGEkdGlzc3VlX3R5cGUpDQpgYGANCmBgYHtyfQ0KIyBERVNlcTIgdXNlcyB0aGUgZmlyc3QgZmFjdG9yIGxldmVsIGFzIHJlZmVyZW5jZQ0KbGV2ZWxzKGNvbERhdGEkdGlzc3VlX3R5cGUpICAjIHJlZmVyZW5jZSA9ICJjb2xvcmVjdGFsIGNhbmNlciBtZXRhc3RhdGljIGluIHRoZSBsaXZlciINCiMgQ29tcGFyaXNvbnM6ICJub3JtYWwiIGFuZCAicHJpbWFyeSB0dW1vciIgdnMgcmVmZXJlbmNlDQpgYGANCmBgYHtyfQ0KIyBXZSB3YW50IHRvIGNvbXBhcmUgImNvbG9yZWN0YWwgY2FuY2VyIG1ldGFzdGF0aWMgaW4gdGhlIGxpdmVyIiBhbmQgICJwcmltYXJ5IHR1bW9yIiBhZ2FpbnN0ICJub3JtYWwiIGluc3RlYWQNCiMgNi4gQ2hhbmdlIGxldmVscyBvcmRlciBzdWNoIHRoYXQgIHJlZmVyZW5jZSA9ICJub3JtYWwiDQpsZXZlbHMoY29sRGF0YSR0aXNzdWVfdHlwZSkgPC0gYygNCiAgIm5vcm1hbCIsDQogICJwcmltYXJ5IHR1bW9yIiwNCiAgImNvbG9yZWN0YWwgY2FuY2VyIG1ldGFzdGF0aWMgaW4gdGhlIGxpdmVyIg0KKQ0KbGV2ZWxzKGNvbERhdGEkdGlzc3VlX3R5cGUpDQpgYGANCmBgYHtyfQ0KIyBDcmVhdGUgdGhlIERFc2VxMkRhdGFTZXQgb2JqZWN0DQojIERlc2lnbjogY29udHJvbCB2YXJpYWJsZSBmaXJzdCwgY29uZGl0aW9uIG9mIGludGVyZXN0IHNlY29uZA0KIyBERVNlcTIgYWRqdXN0cyBmb3IgaW5kaXZpZHVhbCB2YXJpYXRpb24gYmVmb3JlIHRlc3RpbmcgdGlzc3VlX3R5cGUgZGlmZmVyZW5jZXMNCmRkcyA8LSBERVNlcURhdGFTZXRGcm9tTWF0cml4KGNvdW50RGF0YSA9IHJhd0NvdW50cywgY29sRGF0YSA9IGNvbERhdGEsIGRlc2lnbiA9IH4gaW5kaXZpZHVhbF9pZCArIHRpc3N1ZV90eXBlKQ0KDQpgYGANCg0KYGBge3J9DQpkZHMgIyBwcmludHMgc3VtbWFyeSBvZiB0aGUgREVTZXFEYXRhU2V0DQoNCmBgYA0KYGBge3J9DQpjb2xEYXRhKGRkcykgICMgc2hvd3Mgc2FtcGxlIGFubm90YXRpb25zIChlLmcuLCB0aXNzdWVfdHlwZSwgaW5kaXZpZHVhbF9pZCkNCg0KYGBgDQpgYGB7cn0NCmNvdW50cyhkZHMpWzE6NSwgMTo1XSAgIyB2aWV3IGZpcnN0IGZldyBnZW5lcyBhbmQgc2FtcGxlcw0KYGBgDQpgYGB7cn0NCg0KIyAgUnVuIHRoZSBERVNlcSBwaXBlbGluZQ0KZGRzIDwtIERFU2VxKGRkcykNCmBgYA0KDQpgYGB7cn0NCiMgVmFyaWFuY2UgU3RhYmlsaXppbmcgVHJhbnNmb3JtYXRpb24gYW5kICBwbG90IFBDQQ0KdnNkIDwtIHZzdChkZHMsIGJsaW5kID0gRkFMU0UpDQpwbG90UENBKHZzZCwgaW50Z3JvdXA9InRpc3N1ZV90eXBlIikrIHRoZW1lX2dyZXkoKQ0KYGBgDQpgYGB7cn0NCiMgTm90ZTogY29udHJhc3QgPSBjKGZhY3RvciwgbnVtZXJhdG9yLCBkZW5vbWluYXRvcikNCiMgbWVhbnMgQ29tcHV0ZSBsb2cyIGZvbGQgY2hhbmdlIGFzIG51bWVyYXRvciAvIGRlbm9taW5hdG9yLg0KIyBjb250cmFzdCA9IGMoInRpc3N1ZV90eXBlIiwgInByaW1hcnkgdHVtb3IiLCAibm9ybWFsIik6IHBvc2l0aXZlIHZhbHVlcyBtZWFuIGhpZ2hlciBpbiB0dW1vciwgbmVnYXRpdmUgbWVhbiBoaWdoZXIgaW4gbm9ybWFsDQojIGNvbnRyYXN0ID0gYygidGlzc3VlX3R5cGUiLCAibm9ybWFsIiwgInByaW1hcnkgdHVtb3IiKTogcG9zaXRpdmUgdmFsdWVzIG1lYW4gaGlnaGVyIGluIG5vcm1hbCwgbmVnYXRpdmUgbWVhbiBoaWdoZXIgaW4gdHVtb3INCmBgYA0KDQpgYGB7cn0NCiMgRXh0cmFjdCByZXN1bHRzDQpyZXMxIDwtIHJlc3VsdHMoZGRzLCBjb250cmFzdCA9IGMoInRpc3N1ZV90eXBlIiwgInByaW1hcnkgdHVtb3IiLCAibm9ybWFsIikpDQpyZXMxDQpgYGANCmBgYHtyfQ0KIyBSZW1vdmUgcm93cyB3aXRoIE5BIA0KcmVzMSA8LSBuYS5vbWl0KHJlczEpDQpyZXMxLmRmIDwtIGFzLmRhdGEuZnJhbWUocmVzMSkNCnJlczEuZGYNCmBgYA0KYGBge3J9DQojIGdldCBnZW5lIG5hbWVzIGZvciBnZW5lIElEcw0KcmVzMS5kZiRzeW1ib2wgPC0gbWFwSWRzKG9yZy5Icy5lZy5kYiwga2V5cyA9IHJvd25hbWVzKHJlczEuZGYpLCBrZXl0eXBlID0gIkVOU0VNQkwiLCBjb2x1bW4gPSAiU1lNQk9MIikNCnJlczEuZGYgPC0gcmVzMS5kZlshaXMubmEocmVzMS5kZiRzeW1ib2wpLF0NCg0KYGBgDQoNCmBgYHtyfQ0KcmVzMiA8LSByZXN1bHRzKGRkcywgY29udHJhc3QgPSBjKCJ0aXNzdWVfdHlwZSIsICJjb2xvcmVjdGFsIGNhbmNlciBtZXRhc3RhdGljIGluIHRoZSBsaXZlciIsICJub3JtYWwiKSkNCnJlczIgPC0gbmEub21pdChyZXMyKQ0KcmVzMi5kZiA8LSBhcy5kYXRhLmZyYW1lKHJlczIpDQpyZXMyLmRmDQpyZXMyLmRmJHN5bWJvbCA8LSBtYXBJZHMob3JnLkhzLmVnLmRiLCBrZXlzID0gcm93bmFtZXMocmVzMi5kZiksIGtleXR5cGUgPSAiRU5TRU1CTCIsIGNvbHVtbiA9ICJTWU1CT0wiKQ0KcmVzMi5kZiA8LSByZXMyLmRmWyFpcy5uYShyZXMyLmRmJHN5bWJvbCksXQ0KYGBgDQoNCmBgYHtyfQ0KIyBWb2xjYW5vIHBsb3RzIDENCkVuaGFuY2VkVm9sY2FubyhyZXMxLmRmLCB4ID0gJ2xvZzJGb2xkQ2hhbmdlJywgeSA9ICdwYWRqJywgbGFiID0gcmVzMS5kZiRzeW1ib2wsDQogICAgICAgICAgICAgICAgcEN1dG9mZiA9IDAuMDUsIEZDY3V0b2ZmID0gMSkNCg0KYGBgDQpgYGB7cn0NCiMgVm9sY2FubyBwbG90cyAyDQpFbmhhbmNlZFZvbGNhbm8ocmVzMi5kZiwgeCA9ICdsb2cyRm9sZENoYW5nZScsIHkgPSAncGFkaicsIGxhYiA9IHJlczIuZGYkc3ltYm9sLA0KICAgICAgICAgICAgICAgIHBDdXRvZmYgPSAwLjA1LCBGQ2N1dG9mZiA9IDEpDQpgYGANCmBgYHtyfQ0KIyBWb2xjYW5vIHBsb3RzIHVzaW5nIGdncGxvdDINCg0KcmVzMS5kZiRleHByZXNzaW9uIDwtICJOT1QiDQpyZXMxLmRmJGV4cHJlc3Npb25bcmVzMS5kZiRwYWRqIDwgMC4wMSAmIHJlczEuZGYkbG9nMkZvbGRDaGFuZ2UgPiAyXSA8LSAiVVAiDQpyZXMxLmRmJGV4cHJlc3Npb25bcmVzMS5kZiRwYWRqIDwgMC4wMSAmIHJlczEuZGYkbG9nMkZvbGRDaGFuZ2UgPCAtMl0gPC0gIkRPV04iDQoNCmdncGxvdChkYXRhID0gcmVzMS5kZiwgYWVzKHggPSBsb2cyRm9sZENoYW5nZSwgeSA9IC1sb2cxMChwYWRqKSwgY29sID0gZXhwcmVzc2lvbikpICsNCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gYygtMiwgMiksIGNvbCA9ICJncmF5IiwgbGluZXR5cGUgPSAnZGFzaGVkJykgKw0KICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAtbG9nMTAoMC4wMSksIGNvbCA9ICJncmF5IiwgbGluZXR5cGUgPSAnZGFzaGVkJykgKw0KICBnZW9tX3BvaW50KHNpemUgPSAyKSArDQogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjKCIjMzQ5OERCIiwgImdyZXkiLCAiI0U3NEMzQyIpLCANCiAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IGMoIkRvd25yZWd1bGF0ZWQiLCAiTm90IHNpZ25pZmljYW50IiwgIlVwcmVndWxhdGVkIikpICsNCiAgdGhlbWVfY2xhc3NpYygpDQpgYGANCmBgYHtyfQ0KcmVzMi5kZiRleHByZXNzaW9uIDwtICJOT1QiDQpyZXMyLmRmJGV4cHJlc3Npb25bcmVzMi5kZiRwYWRqIDwgMC4wMSAmIHJlczIuZGYkbG9nMkZvbGRDaGFuZ2UgPiAyXSA8LSAiVVAiDQpyZXMyLmRmJGV4cHJlc3Npb25bcmVzMi5kZiRwYWRqIDwgMC4wMSAmIHJlczIuZGYkbG9nMkZvbGRDaGFuZ2UgPCAtMl0gPC0gIkRPV04iDQoNCmdncGxvdChkYXRhID0gcmVzMi5kZiwgYWVzKHggPSBsb2cyRm9sZENoYW5nZSwgeSA9IC1sb2cxMChwYWRqKSwgY29sID0gZXhwcmVzc2lvbikpICsNCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gYygtMiwgMiksIGNvbCA9ICJncmF5IiwgbGluZXR5cGUgPSAnZGFzaGVkJykgKw0KICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAtbG9nMTAoMC4wMSksIGNvbCA9ICJncmF5IiwgbGluZXR5cGUgPSAnZGFzaGVkJykgKw0KICBnZW9tX3BvaW50KHNpemUgPSAyKSArDQogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjKCIjMzQ5OERCIiwgImdyZXkiLCAiI0U3NEMzQyIpLCANCiAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IGMoIkRvd25yZWd1bGF0ZWQiLCAiTm90IHNpZ25pZmljYW50IiwgIlVwcmVndWxhdGVkIikpICsNCiAgdGhlbWVfY2xhc3NpYygpDQpgYGANCg0KYGBge3J9DQojIEZpbmQgc2lnbmlmaWNhbnQgZ2VuZXMNCnNpZ3MxLmRmIDwtIHJlczEuZGZbcmVzMS5kZiRwYWRqIDwgMC4wMSxdDQpzaWdzMS5kZiA8LSBzaWdzMS5kZlsoYWJzKHNpZ3MxLmRmJGxvZzJGb2xkQ2hhbmdlKSA+IDIpLF0NCnNpZ3MxLmRmIDwtIHNpZ3MxLmRmW3NpZ3MxLmRmJGJhc2VNZWFuID4gNTAsXSAjIC0gIGF2ZXJhZ2Ugbm9ybWFsaXplZCBjb3VudCBmb3IgYSBnZW5lIGFjcm9zcyBhbGwgc2FtcGxlcw0Kc2lnczEuZGYNCg0KDQpgYGANCg0KYGBge3J9DQpzaWdzMi5kZiA8LSByZXMyLmRmW3JlczIuZGYkcGFkaiA8IDAuMDEsXQ0Kc2lnczIuZGYgPC0gc2lnczIuZGZbKGFicyhzaWdzMi5kZiRsb2cyRm9sZENoYW5nZSkgPiAyKSxdDQpzaWdzMi5kZiA8LSBzaWdzMi5kZltzaWdzMi5kZiRiYXNlTWVhbiA+IDUwLF0gDQpzaWdzMi5kZg0KYGBgDQoNCmBgYHtyfQ0KIyBDb21iaW5lIHNpZ25pZmljYW50IGdlbmVzIGZyb20gYm90aCBjb21wYXJpc29ucw0Kc2lnc19jb21iaW5lZCA8LSByYmluZChzaWdzMS5kZiwgc2lnczIuZGYpDQojIEdldCB1bmlxdWUgZ2VuZSBJRHMNCnVuaXF1ZV9nZW5lcyA8LSB1bmlxdWUoYyhyb3duYW1lcyhzaWdzMS5kZiksIHJvd25hbWVzKHNpZ3MyLmRmKSkpDQojIFN1YnNldCBieSB1bmlxdWUgcm93bmFtZXMNCnNpZ3NfY29tYmluZWQgPC0gc2lnc19jb21iaW5lZFt1bmlxdWVfZ2VuZXMsIF0NCmBgYA0KDQpgYGB7cn0NCiMgRXh0cmFjdCBub3JtYWxpemVkIGNvdW50cyBmb3IgdGhvc2UgZ2VuZXMNCm1hdCA8LSBjb3VudHMoZGRzLCBub3JtYWxpemVkPVQpW3Jvd25hbWVzKHNpZ3NfY29tYmluZWQpLF0gIyBvciBtYXQgPC0gYXNzYXkodnNkKVtyb3duYW1lcyhzaWdzX2NvbWJpbmVkKSwgXQ0KIyBaLXNjb3JlIHRyYW5zZm9ybSBlYWNoIGdlbmUgYWNyb3NzIHNhbXBsZXMNCm1hdC56IDwtIHQoYXBwbHkobWF0LCAxLCBzY2FsZSkpDQpjb2xuYW1lcyhtYXQueikgPC0gcm93bmFtZXMoY29sRGF0YSkgDQpgYGANCg0KYGBge3J9DQp0aXNzdWUgPC0gY29sRGF0YSR0aXNzdWVfdHlwZQ0KDQojIENvbG9yIG1hcA0KdGlzc3VlX2NvbG9yIDwtIGMoDQogICJwcmltYXJ5IHR1bW9yIiA9ICJkYXJrb3JhbmdlIiwNCiAgIm5vcm1hbCIgPSAiZm9yZXN0Z3JlZW4iLA0KICAiY29sb3JlY3RhbCBjYW5jZXIgbWV0YXN0YXRpYyBpbiB0aGUgbGl2ZXIiID0gImZpcmVicmljayINCikNCg0KaGEgPC0gSGVhdG1hcEFubm90YXRpb24oDQogIFRpc3N1ZSA9IHRpc3N1ZSwNCiAgY29sID0gbGlzdChUaXNzdWUgPSB0aXNzdWVfY29sb3IpDQopDQpgYGANCg0KYGBge3J9DQojIFBsb3QgSGVhdG1hcA0KSGVhdG1hcCgNCiAgbWF0LnosDQogIG5hbWUgPSAiWi1zY29yZSIsDQogIHRvcF9hbm5vdGF0aW9uID0gaGEsDQogIGNsdXN0ZXJfcm93cyA9IFRSVUUsDQogIGNsdXN0ZXJfY29sdW1ucyA9IFRSVUUsDQogIHNob3dfY29sdW1uX25hbWVzID0gVFJVRSwNCiAgc2hvd19yb3dfbmFtZXMgPSBGQUxTRSwNCiAgcm93X2xhYmVscyA9IHNpZ3NfY29tYmluZWRbcm93bmFtZXMobWF0LnopLCAic3ltYm9sIl0sIyB1c2VmdWwgd2hlbiBhYm92ZSBpcyBUUlVFDQogIHJvd19uYW1lc19ncCA9IGdwYXIoZm9udHNpemUgPSA2KSwNCiAgY29sdW1uX25hbWVzX2dwID0gZ3Bhcihmb250c2l6ZSA9IDYpLA0KICBoZWF0bWFwX2xlZ2VuZF9wYXJhbSA9IGxpc3QodGl0bGUgPSAiWi1zY29yZSIsIGxlZ2VuZF9kaXJlY3Rpb24gPSAiaG9yaXpvbnRhbCIpDQopDQpgYGANCmBgYHtyfQ0KIyBHZXQgdG9wIDUwIGdlbmVzIGZyb20gYm90aCBjb21wYXJpc29ucw0KdG9wR2VuZXMxIDwtIGhlYWQoc2lnczEuZGZbb3JkZXIoYWJzKHNpZ3MxLmRmJGxvZzJGb2xkQ2hhbmdlKSwgZGVjcmVhc2luZyA9IFRSVUUpLCBdLCA1MCkNCnRvcEdlbmVzMiA8LSBoZWFkKHNpZ3MyLmRmW29yZGVyKGFicyhzaWdzMi5kZiRsb2cyRm9sZENoYW5nZSksIGRlY3JlYXNpbmcgPSBUUlVFKSwgXSwgNTApDQpgYGANCg0KYGBge3J9DQojIENvbWJpbmUgYm90aCBzZXRzDQp0b3BHZW5lc19jb21iaW5lZCA8LSByYmluZCh0b3BHZW5lczEsIHRvcEdlbmVzMikNCiMgR2V0IHVuaXF1ZSBFTlNFTUJMIElEcw0KdW5pcXVlX2dlbmVzIDwtIHVuaXF1ZShjKHJvd25hbWVzKHRvcEdlbmVzMSksIHJvd25hbWVzKHRvcEdlbmVzMikpKQ0KIyBTdWJzZXQgYnkgdW5pcXVlIHJvd25hbWVzDQp0b3BHZW5lc19jb21iaW5lZCA8LSB0b3BHZW5lc19jb21iaW5lZFt1bmlxdWVfZ2VuZXMsIF0NCmBgYA0KDQpgYGB7cn0NCiMgRXh0cmFjdCBub3JtYWxpemVkIGNvdW50cyBmb3IgdGhvc2UgZ2VuZXMNCnRvcG1hdCA8LSBjb3VudHMoZGRzLCBub3JtYWxpemVkPVQpW3Jvd25hbWVzKHRvcEdlbmVzX2NvbWJpbmVkKSxdIA0KdG9wbWF0LnogPC0gdChhcHBseSh0b3BtYXQsIDEsIHNjYWxlKSkgIyBaIHNjb3JlIHRyYW5zZm9ybWF0aW9uIG9mIGdlbmVzIGFjcm9zcyBzYW1wbGVzLg0KY29sbmFtZXModG9wbWF0LnopIDwtIHJvd25hbWVzKGNvbERhdGEpDQpgYGANCg0KYGBge3J9DQpIZWF0bWFwKA0KICB0b3BtYXQueiwNCiAgbmFtZSA9ICJaLXNjb3JlIiwNCiAgdG9wX2Fubm90YXRpb24gPSBoYSwNCiAgY2x1c3Rlcl9yb3dzID0gVFJVRSwNCiAgY2x1c3Rlcl9jb2x1bW5zID0gVFJVRSwNCiAgc2hvd19jb2x1bW5fbmFtZXMgPSBUUlVFLA0KICBzaG93X3Jvd19uYW1lcyA9IFRSVUUsDQogIHJvd19sYWJlbHMgPSB0b3BHZW5lc19jb21iaW5lZFtyb3duYW1lcyh0b3BtYXQueiksICJzeW1ib2wiXSwNCiAgcm93X25hbWVzX2dwID0gZ3Bhcihmb250c2l6ZSA9IDYpLA0KICBjb2x1bW5fbmFtZXNfZ3AgPSBncGFyKGZvbnRzaXplID0gNiksDQogIGhlYXRtYXBfbGVnZW5kX3BhcmFtID0gbGlzdCh0aXRsZSA9ICJaLXNjb3JlIiwgbGVnZW5kX2RpcmVjdGlvbiA9ICJob3Jpem9udGFsIikNCikNCmBgYA0KDQo=